package net.avcompris.binding.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;
import static net.avcompris.binding.impl.AbstractBinderInvocationHandler.getAbstractBinderInvocationHandler;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

import net.avcompris.binding.Binding;
import net.avcompris.binding.Serializer;
import net.avcompris.binding.annotation.XPath;

import com.avcompris.common.annotation.Nullable;
import com.avcompris.lang.NotImplementedException;

final class BindingSpecifics<T, U> extends AbstractInstance<T> implements
		Binding<T>, InvocationHandler {

	public static <T, U> T newProxy(final T delegate, final U node,
			final ClassLoader classLoader, Class<T> clazz) {

		nonNullArgument(delegate, "delegate");
		nonNullArgument(node, "node");
		nonNullArgument(classLoader, "classLoader");
		nonNullArgument(clazz, "clazz");

		final InstanceUpdater<U> invocationHandler = getAbstractBinderInvocationHandler(
				delegate, node);

		final BindingSpecifics<T, U> bindingSpecifics = new BindingSpecifics<T, U>(
				node, delegate, // classLoader,
				clazz, invocationHandler);

		final Class<?>[] interfaces = Binding.class.equals(clazz) //
		? new Class<?>[] { Binding.class } //
				: new Class<?>[] { clazz, Binding.class };

		final Object proxy = Proxy.newProxyInstance(classLoader, interfaces,
				bindingSpecifics);

		final T instance = clazz.cast(proxy);

		bindingSpecifics.setInstance(instance);

		invocationHandler.setInstance(instance);

		return instance;
	}

	private final U node;
	private final T delegate;
	private final Class<T> clazz;
	private final ElementUpdater<U> elementUpdater;

	public BindingSpecifics(final U node, final T delegate,
			final Class<T> clazz, final ElementUpdater<U> elementUpdater) {

		this.node = nonNullArgument(node, "node");
		this.delegate = nonNullArgument(delegate, "delegate");
		this.clazz = nonNullArgument(clazz, "clazz");
		this.elementUpdater = nonNullArgument(elementUpdater, "elementUpdater");
	}

	@Override
	public final Object invoke(final Object proxy, final Method method,
			@Nullable final Object[] args) throws Throwable {

		nonNullArgument(proxy, "proxy");
		nonNullArgument(method, "method");

		if (Binding.class.equals(method.getDeclaringClass())) {

			try {

				return method.invoke(this, args);

			} catch (final InvocationTargetException e) {

				final Throwable targetException = e.getTargetException();

				throw targetException == null ? e : targetException;
			}

		} else {

			method.setAccessible(true);

			try {

				return method.invoke(delegate, args);

			} catch (final InvocationTargetException e) {

				final Throwable targetException = e.getTargetException();

				throw targetException == null ? e : targetException;
			}
		}
	}

	@Override
	public Object attribute(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public T attribute(final String name, @Nullable final Object value) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public Map<String, Object> attributes() {

		throw new NotImplementedException();
	}

	@Override
	public <V> V child(final int index, final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T child(final int index) {

		throw new NotImplementedException();
	}

	@Override
	public <V> V[] children(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T[] children() {

		throw new NotImplementedException();
	}

	@Override
	public T removeAttributes() {

		for (final String name : elementUpdater.getAttributes(node).keySet()) {

			elementUpdater.removeAttribute(node, name);
		}

		return getInstance();
	}

	@Override
	public T removeChildren() {

		for (final U child : elementUpdater.getChildren(node)) {

			elementUpdater.remove(child);
		}

		return getInstance();
	}

	@Override
	public T clear() {

		removeAttributes();

		removeChildren();

		return getInstance();
	}

	@Override
	public <V> V firstChild(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T firstChild() {

		throw new NotImplementedException();
	}

	@Override
	public <V> V followingSibling(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T followingSibling() {

		throw new NotImplementedException();
	}

	@Override
	public <V> V lastChild(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T lastChild() {

		throw new NotImplementedException();
	}

	@Override
	public void moveAfter(final Object previous) {

		nonNullArgument(previous, "previous");

		throw new NotImplementedException();
	}

	@Override
	public void moveAfterLastChildOf(final Object object) {

		nonNullArgument(object, "object");

		throw new NotImplementedException();
	}

	@Override
	public void moveBefore(final Object next) {

		nonNullArgument(next, "next");

		throw new NotImplementedException();
	}

	@Override
	public void moveBeforeFirstChildOf(final Object object) {

		nonNullArgument(object, "object");

		throw new NotImplementedException();
	}

	@Override
	public void moveInLieuOf(final Object object) {

		nonNullArgument(object, "object");

		throw new NotImplementedException();
	}

	@Override
	public String name() {

		return elementUpdater.getName(node);
	}

	@Override
	public String namespaceURI() {

		throw new NotImplementedException();
	}

	@Override
	public boolean noChild() {

		throw new NotImplementedException();
	}

	@Override
	public <V> V parent(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T parent() {

		throw new NotImplementedException();
	}

	@Override
	public <V> V precedingSibling(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T precedingSibling() {

		throw new NotImplementedException();
	}

	@Override
	public final T self() {

		return getInstance();
	}

	@Override
	public final <V> V self(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		if (clazz.equals(this.clazz)) {

			return clazz.cast(getInstance());
		}

		throw new NotImplementedException();
	}

	@Override
	public T remove() {

		throw new NotImplementedException();
	}

	@Override
	public T rename(final String name) {

		nonNullArgument(name, "name");

		elementUpdater.setName(node, name);

		return getInstance();
	}

	@Override
	public T resetContent(final String content) {

		nonNullArgument(content, "content");

		throw new NotImplementedException();
	}

	@Override
	public <V> V root(final Class<V> clazz) {

		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T root() {

		throw new NotImplementedException();
	}

	@Override
	public void serialize() throws IOException {

		if (this.serializer == null) {
			throw new IllegalStateException(
					"Default serializer has not been set.");
		}

		serializeUsing(this.serializer);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void serializeUsing(final Serializer<?> serializer)
			throws IOException {

		nonNullArgument(serializer, "serializer");

		((Serializer<U>) serializer).serialize(node);
	}

	@Override
	public T useSerializer(@Nullable final Serializer<?> serializer) {

		this.serializer = serializer;

		return getInstance();
	}

	private Serializer<?> serializer = null;

	@Override
	@Nullable
	public Serializer<?> serializer() {

		return serializer;
	}

	@Override
	public String textContent() {

		throw new NotImplementedException();
	}

	@Override
	public String type() {

		throw new NotImplementedException();
	}

	@Override
	public T clone() {

		throw new NotImplementedException();
	}

	@Override
	public <V> V child(final String name, final int index, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T child(final String name, final int index) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public <V> V[] children(final String name, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T[] children(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public T removeChildren(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public <V> V firstChild(final String name, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T firstChild(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public <V> V followingSibling(final String name, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T followingSibling(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public <V> V lastChild(final String name, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T lastChild(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public boolean noChild(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public <V> V parent(final String name, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T parent(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public <V> V precedingSibling(final String name, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T precedingSibling(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public <V> V root(final String name, final Class<V> clazz) {

		nonNullArgument(name, "name");
		nonNullArgument(clazz, "class");

		throw new NotImplementedException();
	}

	@Override
	public T root(final String name) {

		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public T evaluate(final String xpathExpression, final Object... args) {

		nonNullArgument(clazz, "class");
		nonNullArgument(xpathExpression, "xpathExpression");
		nonNullArgument(args, "args");

		throw new NotImplementedException();
	}

	@Override
	public <V> V evaluate(final Class<V> clazz, final String xpathExpression,
			final Object... args) {

		nonNullArgument(clazz, "class");
		nonNullArgument(xpathExpression, "xpathExpression");
		nonNullArgument(args, "args");

		throw new NotImplementedException();
	}

	@Override
	public T evaluate(final XPath xpath, final Object... args) {

		nonNullArgument(clazz, "class");
		nonNullArgument(xpath, "xpath");
		nonNullArgument(args, "args");

		throw new NotImplementedException();
	}

	@Override
	public <V> V evaluate(final Class<V> clazz, final XPath xpath,
			final Object... args) {

		nonNullArgument(clazz, "class");
		nonNullArgument(xpath, "xpath");
		nonNullArgument(args, "args");

		throw new NotImplementedException();
	}

	@Override
	public Object node() {

		return node;
	}
}
